package com.example.redis.config;

import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;

@EnableCaching
@Configuration
//@EnableConfigurationProperties(CacheProperties.class)
// 由于我们使用了自定义 RedisCacheManager 缓存管理器，导致默认的 CacheManager 缓存管理器失效，缓存配置文件 CacheProperties 也没有注册到 Spring 容器中，所以需要单独配置。
public class CacheConfig {

    // Spring 缓存抽象的配置和使用参考 https://docs.springjava.cn/spring-framework/reference/integration/cache.html
    // SpringBoot 关于缓存文档 https://docs.springjava.cn/spring-boot/reference/io/caching.html#io.caching.provider.redis
    // Spring Data Redis 的配置和使用 https://docs.springjava.cn/spring-data/redis/reference/index.html
    // 配置 RedisCacheManager 缓存管理器，如果配置了 RedisCacheManager 缓存管理器，则默认的 CacheManager 缓存管理器失效，缓存配置文件 CacheProperties 也没有注册到 Spring 容器中，所以需要单独配置。
    // 不要将所有缓存统统放在一个 Redis 数据库中，我们可以将应用 Cache 缓存单独存放在一个 Redis 数据库中，然后将短信验证码单独放在一个 Redis 数据库中。避免混乱
    // Redis 缓存对象内容的序列化不使用 RedisTemplate 的配置，需要在 RedisCacheManager 中使用 serializeValuesWith 单独配置，否则缓存 Cache 对象内容值会乱码
    // 此处以 Java 代码的方式配置了 RedisCacheManager，也可以使用 Spring Boot 的配置文件配置。但两者不是互补的，此处配置了，配置文件中的redis缓存就失效了
    // 使用应用配置文件存在的不足在于 Cache 缓存对象内容值经过序列化之后存储在 Redis 中会乱码，需要如下使用 serializeValuesWith 方法指定序列化方式。

    // 以下配置参考 https://docs.springjava.cn/spring-data/redis/reference/redis/redis-cache.html#redis:support:cache-abstraction:expiration
    // @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory, CacheProperties cacheProperties) {
        // 创建Redis缓存自定义配置，只对Cache缓存对象有效，对于RedisTemplate无效
        RedisCacheConfiguration config = RedisCacheConfiguration
                // 默认配置中，缓存对象 Cache 的内容序列化采用了 RedisSerializer.java 下面改为 RedisSerializer.json()
                .defaultCacheConfig()
                // Cache缓存的值序列化方式，此处采用 json
                .serializeValuesWith(
                        RedisSerializationContext
                                .SerializationPair
                                // Spring Data Redis 序列化器相关参考 https://docs.springjava.cn/spring-data/redis/reference/redis/template.html#redis:serializer
                                .fromSerializer(RedisSerializer.json()))
                // Cache缓存的名称统一前缀，建议搞个常量类，不要写死在代码里
                .prefixCacheNameWith(cacheProperties.getRedis().getKeyPrefix())
                // Cache缓存的统一过期时间，同理
                .entryTtl(cacheProperties.getRedis().getTimeToLive());

        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
    }

}

// springboot 中使用 redis 缓存注解参考官网 https://java.cunzaima.cn/spring-boot_docs_3.2.3-zh-all/io.html#io
// 一个 Cache 缓存的键的组成格式为 prefix + cacheName + key 三部分组成，其中 prefix 为缓存键的前缀，cacheName 就是 @CacheConfig 中配置的 cacheNames，key 就是方法参数或者方法返回值。
// 例如：@Cacheable(cacheNames = "userCache", key = "#userId")，则最终的缓存键为：userCache:12345

// 一个缓存对象在Redis中存储的名称格式是： 前缀 + CacheName:key
// 假如：前缀为 blog_	  CacheName为 userCache 	 key为 12345	  则最终在Redis中存储的名称为： blog_userCache:12345

// 注册 RedisCacheManager 组件相当于配置spring.cache.type=redis	两者只可选其一。不要同时配置，避免造成不必要的冲突。如果同时配置了，会使用自定义的配置。
// 配置了spring.cache.type=redis，Spring Boot会自动创建一个默认的RedisCacheManager bean，并将其用作默认的缓存管理器。